package com.talent.aop;

import com.alibaba.fastjson.JSON;
import com.talent.basecontroller.exception.DuplicateSubmitException;
import com.talent.interceptor.FormToken;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 使用切面来做防止页面重复提交
 * Created by zhumenglong on 2017/5/16.
 */
@Aspect
@Component
public class FormTokenAspect {
    /**
     * 日志记录
     */
    private static final Logger logger = LoggerFactory.getLogger(FormTokenAspect.class);

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Pointcut("@annotation(com.talent.interceptor.FormToken)")
    public void doAspect() {
        System.out.println("我是一个切入点");
    }

    @Before("doAspect()")
    public void doBefore(JoinPoint pjp) throws Exception {
        Object[] args = pjp.getArgs();
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        Object target = pjp.getTarget();
        String params = JSON.toJSONString(args);
        FormToken annotation = method.getAnnotation(FormToken.class);


        String clinetTokenValue = parseToken(annotation.param(), method, args);

        if (logger.isDebugEnabled()) {
            logger.debug("before methodName: {}", method.getName());
            logger.debug("before args: {}", params);
            logger.debug("before target: {}", target);
        }

        boolean flag = method.isAnnotationPresent(FormToken.class);
        if (flag) {
            if(checkCanInvokeMethod(clinetTokenValue,annotation)){
                if (isRepeatSubmit(clinetTokenValue)) {
                    throw new DuplicateSubmitException("重复提交表单。");
                }
            }else{
                throw new DuplicateSubmitException("调用方法非法。");
            }
        }
    }

    @After("doAspect()")
    public void doAfter(JoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        FormToken annotation = method.getAnnotation(FormToken.class);

        String method1 = annotation.method();
        redisTemplate.opsForHash().delete("sessionId",method1);
    }

    /**
     * 判断是否是重复请求
     *
     * @param clinetTokenValue
     * @return boolena
     */
    private boolean isRepeatSubmit(String clinetTokenValue) {
        String[] split = clinetTokenValue.split("_");
        String replace = split[1].replace("_", "");
        String serverTokenValue=(String)redisTemplate.opsForHash().get("sessionId",split[0]);
        
        if (serverTokenValue == null) {
            return true;
        }
        if (replace == null) {
            return true;
        }
        if (!serverTokenValue.equals(replace)) {
            return true;
        }
        return false;
    }

    /**
     * 判断提交的token是否可以调用当前的方法</P>
     * 即比较token的前半部分和注解上的method的值是否相同
     *
     * @return 可以调用返回TRUE
     */
    private Boolean checkCanInvokeMethod(String clinetTokenValue,FormToken annotation){
        String[] split = clinetTokenValue.split("_");
        String requestMethod = split[0];//该token可以调用的方法名
        String annotationMethod = annotation.method();//实际调用的方法名
        return  requestMethod.equals(annotationMethod);
    }

    /**
     * 返回以key作为参数名的方法的参数值。
     *
     * @param key FormToken注解上的param的值
     * @param method 方法
     * @param args 方法参数列表
     *
     * @return 返回以key作为参数名的方法的参数值。
     */
    private String  parseToken(String key, Method method, Object[] args){
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();

        String[] paraNameArr = u.getParameterNames(method);

        SpelExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();

        for (int i = 0; i < paraNameArr.length; ++i) {
            context.setVariable(paraNameArr[i], args[i]);
        }

        return parser.parseExpression(key).getValue(context, String.class);
    }


}
